﻿using FFMpegCore.Enums;
using FFMpegCore.Exceptions;
using FFMpegCore.Extend;
using FFMpegCore.Pipes;
using FFMpegCore.Test.Resources;

namespace FFMpegCore.Test;

[TestClass]
public class AudioTest
{
    private const int BaseTimeoutMilliseconds = 30_000;

    public TestContext TestContext { get; set; }

    [TestMethod]
    public void Audio_Remove()
    {
        using var outputFile = new TemporaryFile("out.mp4");

        FFMpeg.Mute(TestResources.Mp4Video, outputFile);
        var analysis = FFProbe.Analyse(outputFile);

        Assert.IsNotEmpty(analysis.VideoStreams);
        Assert.IsEmpty(analysis.AudioStreams);
    }

    [TestMethod]
    public void Audio_Save()
    {
        using var outputFile = new TemporaryFile("out.mp3");

        FFMpeg.ExtractAudio(TestResources.Mp4Video, outputFile);
        var analysis = FFProbe.Analyse(outputFile);

        Assert.IsNotEmpty(analysis.AudioStreams);
        Assert.IsEmpty(analysis.VideoStreams);
    }

    [TestMethod]
    public async Task Audio_FromRaw()
    {
        await using var file = File.Open(TestResources.RawAudio, FileMode.Open);
        var memoryStream = new MemoryStream();
        await FFMpegArguments
            .FromPipeInput(new StreamPipeSource(file), options => options.ForceFormat("s16le"))
            .OutputToPipe(new StreamPipeSink(memoryStream), options => options.ForceFormat("mp3"))
            .CancellableThrough(TestContext.CancellationToken)
            .ProcessAsynchronously();
    }

    [TestMethod]
    public void Audio_Add()
    {
        using var outputFile = new TemporaryFile("out.mp4");

        var success = FFMpeg.ReplaceAudio(TestResources.Mp4WithoutAudio, TestResources.Mp3Audio, outputFile);
        var videoAnalysis = FFProbe.Analyse(TestResources.Mp4WithoutAudio);
        var audioAnalysis = FFProbe.Analyse(TestResources.Mp3Audio);
        var outputAnalysis = FFProbe.Analyse(outputFile);

        Assert.IsTrue(success);
        Assert.AreEqual(Math.Max(videoAnalysis.Duration.TotalSeconds, audioAnalysis.Duration.TotalSeconds), outputAnalysis.Duration.TotalSeconds, 0.15);
        Assert.IsTrue(File.Exists(outputFile));
    }

    [TestMethod]
    public void Image_AddAudio()
    {
        using var outputFile = new TemporaryFile("out.mp4");
        FFMpeg.PosterWithAudio(TestResources.PngImage, TestResources.Mp3Audio, outputFile);
        var analysis = FFProbe.Analyse(TestResources.Mp3Audio);
        Assert.IsGreaterThan(0, analysis.Duration.TotalSeconds);
        Assert.IsTrue(File.Exists(outputFile));
    }

    [TestMethod]
    [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
    public void Audio_ToAAC_Args_Pipe()
    {
        using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");

        var samples = new List<IAudioSample> { new PcmAudioSampleWrapper([0, 0]), new PcmAudioSampleWrapper([0, 0]) };

        var audioSamplesSource = new RawAudioPipeSource(samples) { Channels = 2, Format = "s8", SampleRate = 8000 };

        var success = FFMpegArguments
            .FromPipeInput(audioSamplesSource)
            .OutputToFile(outputFile, false, opt => opt
                .WithAudioCodec(AudioCodec.Aac))
            .CancellableThrough(TestContext.CancellationToken)
            .ProcessSynchronously();
        Assert.IsTrue(success);
    }

    [TestMethod]
    [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
    public void Audio_ToLibVorbis_Args_Pipe()
    {
        using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");

        var samples = new List<IAudioSample> { new PcmAudioSampleWrapper([0, 0]), new PcmAudioSampleWrapper([0, 0]) };

        var audioSamplesSource = new RawAudioPipeSource(samples) { Channels = 2, Format = "s8", SampleRate = 8000 };

        var success = FFMpegArguments
            .FromPipeInput(audioSamplesSource)
            .OutputToFile(outputFile, false, opt => opt
                .WithAudioCodec(AudioCodec.LibVorbis))
            .CancellableThrough(TestContext.CancellationToken)
            .ProcessSynchronously();
        Assert.IsTrue(success);
    }

    [TestMethod]
    [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
    public async Task Audio_ToAAC_Args_Pipe_Async()
    {
        using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");

        var samples = new List<IAudioSample> { new PcmAudioSampleWrapper([0, 0]), new PcmAudioSampleWrapper([0, 0]) };

        var audioSamplesSource = new RawAudioPipeSource(samples) { Channels = 2, Format = "s8", SampleRate = 8000 };

        var success = await FFMpegArguments
            .FromPipeInput(audioSamplesSource)
            .OutputToFile(outputFile, false, opt => opt
                .WithAudioCodec(AudioCodec.Aac))
            .CancellableThrough(TestContext.CancellationToken)
            .ProcessAsynchronously();
        Assert.IsTrue(success);
    }

    [TestMethod]
    [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
    public void Audio_ToAAC_Args_Pipe_ValidDefaultConfiguration()
    {
        using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");

        var samples = new List<IAudioSample> { new PcmAudioSampleWrapper([0, 0]), new PcmAudioSampleWrapper([0, 0]) };

        var audioSamplesSource = new RawAudioPipeSource(samples);

        var success = FFMpegArguments
            .FromPipeInput(audioSamplesSource)
            .OutputToFile(outputFile, false, opt => opt
                .WithAudioCodec(AudioCodec.Aac))
            .CancellableThrough(TestContext.CancellationToken)
            .ProcessSynchronously();
        Assert.IsTrue(success);
    }

    [TestMethod]
    [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
    public void Audio_ToAAC_Args_Pipe_InvalidChannels()
    {
        using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");

        var audioSamplesSource = new RawAudioPipeSource(new List<IAudioSample>()) { Channels = 0 };

        Assert.ThrowsExactly<FFMpegException>(() => FFMpegArguments
            .FromPipeInput(audioSamplesSource)
            .OutputToFile(outputFile, false, opt => opt
                .WithAudioCodec(AudioCodec.Aac))
            .CancellableThrough(TestContext.CancellationToken)
            .ProcessSynchronously());
    }

    [TestMethod]
    [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
    public void Audio_ToAAC_Args_Pipe_InvalidFormat()
    {
        using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");

        var audioSamplesSource = new RawAudioPipeSource(new List<IAudioSample>()) { Format = "s8le" };

        Assert.ThrowsExactly<FFMpegException>(() => FFMpegArguments
            .FromPipeInput(audioSamplesSource)
            .OutputToFile(outputFile, false, opt => opt
                .WithAudioCodec(AudioCodec.Aac))
            .CancellableThrough(TestContext.CancellationToken)
            .ProcessSynchronously());
    }

    [TestMethod]
    [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
    public void Audio_ToAAC_Args_Pipe_InvalidSampleRate()
    {
        using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");

        var audioSamplesSource = new RawAudioPipeSource(new List<IAudioSample>()) { SampleRate = 0 };

        Assert.ThrowsExactly<FFMpegException>(() => FFMpegArguments
            .FromPipeInput(audioSamplesSource)
            .OutputToFile(outputFile, false, opt => opt
                .WithAudioCodec(AudioCodec.Aac))
            .CancellableThrough(TestContext.CancellationToken)
            .ProcessSynchronously());
    }

    [TestMethod]
    [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
    public void Audio_Pan_ToMono()
    {
        using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");

        var success = FFMpegArguments.FromFileInput(TestResources.Mp3Audio)
            .OutputToFile(outputFile, true,
                argumentOptions => argumentOptions
                    .WithAudioFilters(filter => filter.Pan(1, "c0 < 0.9 * c0 + 0.1 * c1")))
            .CancellableThrough(TestContext.CancellationToken)
            .ProcessSynchronously();

        var mediaAnalysis = FFProbe.Analyse(outputFile);

        Assert.IsTrue(success);
        Assert.HasCount(1, mediaAnalysis.AudioStreams);
        Assert.AreEqual("mono", mediaAnalysis.PrimaryAudioStream!.ChannelLayout);
    }

    [TestMethod]
    [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
    public void Audio_Pan_ToMonoNoDefinitions()
    {
        using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");

        var success = FFMpegArguments.FromFileInput(TestResources.Mp3Audio)
            .OutputToFile(outputFile, true,
                argumentOptions => argumentOptions
                    .WithAudioFilters(filter => filter.Pan(1)))
            .CancellableThrough(TestContext.CancellationToken)
            .ProcessSynchronously();

        var mediaAnalysis = FFProbe.Analyse(outputFile);

        Assert.IsTrue(success);
        Assert.HasCount(1, mediaAnalysis.AudioStreams);
        Assert.AreEqual("mono", mediaAnalysis.PrimaryAudioStream!.ChannelLayout);
    }

    [TestMethod]
    [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
    public void Audio_Pan_ToMonoChannelsToOutputDefinitionsMismatch()
    {
        using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");

        Assert.ThrowsExactly<ArgumentException>(() => FFMpegArguments.FromFileInput(TestResources.Mp3Audio)
            .OutputToFile(outputFile, true,
                argumentOptions => argumentOptions
                    .WithAudioFilters(filter => filter.Pan(1, "c0=c0", "c1=c1")))
            .CancellableThrough(TestContext.CancellationToken)
            .ProcessSynchronously());
    }

    [TestMethod]
    [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
    public void Audio_Pan_ToMonoChannelsLayoutToOutputDefinitionsMismatch()
    {
        using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");

        Assert.ThrowsExactly<FFMpegException>(() => FFMpegArguments.FromFileInput(TestResources.Mp3Audio)
            .OutputToFile(outputFile, true,
                argumentOptions => argumentOptions
                    .WithAudioFilters(filter => filter.Pan("mono", "c0=c0", "c1=c1")))
            .CancellableThrough(TestContext.CancellationToken)
            .ProcessSynchronously());
    }

    [TestMethod]
    [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
    public void Audio_DynamicNormalizer_WithDefaultValues()
    {
        using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");

        var success = FFMpegArguments.FromFileInput(TestResources.Mp3Audio)
            .OutputToFile(outputFile, true,
                argumentOptions => argumentOptions
                    .WithAudioFilters(filter => filter.DynamicNormalizer()))
            .CancellableThrough(TestContext.CancellationToken)
            .ProcessSynchronously();

        Assert.IsTrue(success);
    }

    [TestMethod]
    [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
    public void Audio_DynamicNormalizer_WithNonDefaultValues()
    {
        using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");

        var success = FFMpegArguments.FromFileInput(TestResources.Mp3Audio)
            .OutputToFile(outputFile, true,
                argumentOptions => argumentOptions
                    .WithAudioFilters(filter => filter.DynamicNormalizer(250, 7, 0.9, 2, 1, false, true, true, 0.5)))
            .CancellableThrough(TestContext.CancellationToken)
            .ProcessSynchronously();

        Assert.IsTrue(success);
    }

    [TestMethod]
    [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
    [DataRow(2)]
    [DataRow(32)]
    [DataRow(8)]
    public void Audio_DynamicNormalizer_FilterWindow(int filterWindow)
    {
        using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");

        Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => FFMpegArguments
            .FromFileInput(TestResources.Mp3Audio)
            .OutputToFile(outputFile, true,
                argumentOptions => argumentOptions
                    .WithAudioFilters(filter => filter.DynamicNormalizer(filterWindow: filterWindow)))
            .CancellableThrough(TestContext.CancellationToken)
            .ProcessSynchronously());
    }
}
